//
// Created by guoshunw on 23-12-16.
//

#include "gflags/gflags.h"
#include "log.h"

#include <iostream>

#include "capnp/dynamic.h"
#include "capnp/message.h"
#include "capnp/serialize-packed.h"
#include <capnp/schema.h>

#include "addressbook.capnp.h"

enum WORK_MODE : int32_t {
    READ = 0,
    WRITE,
#if !CAPNP_LITE
    DYNAMIC_READ,
    DYNAMIC_WRITE,
#endif
    UNKNOWN
};

DEFINE_int32(work_mode, UNKNOWN, "The work mode of the addressbook.");

void printAddressBook(int fd);
void writeAddressBook(int fd);
void dynamicWriteAddressBook(int fd, capnp::StructSchema);
void dynamicPrintAddressBook(int fd, capnp::StructSchema);

int main(int argc, char *argv[]) {
    google::InitGoogleLogging(argv[0]);
    atexit([]() {
        google::ShutdownGoogleLogging();
    });

    //    LOG(INFO) << "Start " << argv[0];
    gflags::ParseCommandLineFlags(&argc, &argv, true);
    if (UNKNOWN == FLAGS_work_mode) {

        std::string detail = "work mode 0:READ,1:WRITE";
#if !CAPNP_LITE
        detail += ",2:DYNAMIC_READ,3:DYNAMIC_WRITE";
#endif
        std::cout << "Usage: " << argv[0] << " --work_mode[0|1|2|3]   " << detail << std::endl;
        return EXIT_FAILURE;
    }
    auto fd = (READ == FLAGS_work_mode || DYNAMIC_READ == FLAGS_work_mode) ? fileno(stdin) : fileno(stdout);
    switch (FLAGS_work_mode) {
        case READ:
        case WRITE: {
            READ == FLAGS_work_mode ? printAddressBook(fd) : writeAddressBook(fd);
        } break;
        case DYNAMIC_READ:
        case DYNAMIC_WRITE: {
            auto schema = capnp::Schema::from<addressbook::AddressBook>();
            DYNAMIC_READ == FLAGS_work_mode ? dynamicPrintAddressBook(fd, schema) : dynamicWriteAddressBook(fd, schema);
        } break;
        default:
            std::cerr << "Unknown work mode: " << FLAGS_work_mode << std::endl;
            return EXIT_FAILURE;
    }

    return 0;
}

void dynamicWriteAddressBook(int fd, capnp::StructSchema schema) {
    // Write a message using the dynamic API to set each field by text name.
    // This isn't something you'd normally want to do; it's just for illustration.

    capnp::MallocMessageBuilder message;
    auto addressBook = message.initRoot<capnp::DynamicStruct>(schema);
    auto people = addressBook.init("people", 2).as<capnp::DynamicList>();

    auto alice = people[0].as<capnp::DynamicStruct>();
    alice.set("id", 123);
    alice.set("name", "Alice");
    alice.set("email", "alice@example.com");
    auto alicePhoneNumbers = alice.init("phones", 1).as<capnp::DynamicList>();
    auto phone0 = alicePhoneNumbers[0].as<capnp::DynamicStruct>();
    phone0.set("number", "555-1212");
    phone0.set("type", "mobile");
    alice.get("employment").as<capnp::DynamicStruct>().set("school", "MIT");

    auto bob = people[1].as<capnp::DynamicStruct>();
    bob.set("id", 456);
    bob.set("name", "Bob");
    bob.set("email", "bob@example.com");
    auto bobPhoneNumbers = bob.init("phones", 2).as<capnp::List<addressbook::Person::PhoneNumber>>();

    bobPhoneNumbers[0].setNumber("555-4567");
    bobPhoneNumbers[0].setType(addressbook::Person::PhoneNumber::Type::HOME);
    bobPhoneNumbers[1].setNumber("555-7654");
    bobPhoneNumbers[1].setType(addressbook::Person::PhoneNumber::Type::WORK);
    bob.get("employment").as<capnp::DynamicStruct>().set("unemployed", ::capnp::VOID);

    capnp::writePackedMessageToFd(fd, message);
    LOGI << "Dynamic write address book with " << people.size() << " contacts to fd: " << fd;
}
void dynamicPrintValue(const capnp::DynamicValue::Reader &value, int level = 0) {

    switch (value.getType()) {
        case capnp::DynamicValue::Type::VOID:
            break;
        case capnp::DynamicValue::Type::BOOL:
            std::cout << std::boolalpha << value.as<bool>() << std::noboolalpha;
            break;
        case capnp::DynamicValue::Type::INT:
            std::cout << value.as<int64_t>();
            break;
        case capnp::DynamicValue::Type::UINT:
            std::cout << value.as<uint64_t>();
            break;
        case capnp::DynamicValue::Type::FLOAT:
            std::cout << value.as<double>();
            break;
        case capnp::DynamicValue::Type::TEXT:
            std::cout << '"' << value.as<capnp::Text>().cStr() << '"';
            break;
        case capnp::DynamicValue::Type::ENUM: {
            auto enumValue = value.as<capnp::DynamicEnum>();
            KJ_IF_MAYBE (enumerant, enumValue.getEnumerant()) {
                std::cout << enumerant->getProto().getName().cStr();
            } else {
                std::cout << enumValue.getRaw();
            }
        } break;
        case capnp::DynamicValue::Type::LIST: {
            std::cout << "[";
            auto first = true;
            for (auto element: value.as<capnp::DynamicList>()) {
                if (first) first = false;
                else
                    std::cout << ", ";
                dynamicPrintValue(element, level + 1);
            }
            std::cout << "]";
        } break;
        case capnp::DynamicValue::Type::STRUCT: {
            // 😁
            std::cout << "(" << std::endl;
            auto structValue = value.as<capnp::DynamicStruct>();
            auto indent = std::string(level * 2, ' ');
            for (auto field: structValue.getSchema().getFields()) {
                if (!structValue.has(field)) continue;
                std::cout << indent << field.getProto().getName().cStr() << "=";
                dynamicPrintValue(structValue.get(field), level + 1);
                std::cout << std::endl;
            }
            std::cout << indent << ")";
        } break;
        default:
            std::cout << "?";
            break;
    }
}

void dynamicPrintAddressBook(int fd, capnp::StructSchema schema) {
    capnp::PackedFdMessageReader message(fd);
    // Print an arbitrary message via the dynamic API by
    // iterating over the schema.
    // Look at the handling of STRUCT in particular.

    dynamicPrintValue(message.getRoot<capnp::DynamicStruct>(schema));
    std::cout << std::endl;
}


void writeAddressBook(int fd) {
    capnp::MallocMessageBuilder message;
    auto addressBook = message.initRoot<addressbook::AddressBook>();
    auto people = addressBook.initPeople(2);

    auto alice = people[0];
    alice.setId(123);
    alice.setName("Alice");
    alice.setEmail("alice@example.com");
    auto alicePhoneNumbers = alice.initPhones(1);
    alicePhoneNumbers[0].setNumber("555-1212");
    alicePhoneNumbers[0].setType(addressbook::Person::PhoneNumber::Type::MOBILE);
    alice.getEmployment().setSchool("MIT");

    auto bob = people[1];
    bob.setId(456);
    bob.setName("Bob");
    bob.setEmail("bob@example.com");
    auto bobPhoneNumbers = bob.initPhones(2);
    bobPhoneNumbers[0].setNumber("555-4567");
    bobPhoneNumbers[0].setType(addressbook::Person::PhoneNumber::Type::HOME);
    bobPhoneNumbers[1].setNumber("555-7654");
    bobPhoneNumbers[1].setType(addressbook::Person::PhoneNumber::Type::WORK);
    bob.getEmployment().setUnemployed();

    capnp::writePackedMessageToFd(fd, message);
    LOGI << "Write address book with " << people.size() << " contacts to fd: " << fd;
}

using namespace addressbook;
std::ostream &operator<<(std::ostream &os, const Person::Reader &personReader) {
    os << personReader.getName().cStr() << ": " << personReader.getEmail().cStr() << std::endl;
    for (auto phone: personReader.getPhones()) {
        auto typeName = "UNKNOWN";

        switch (phone.getType()) {
            case Person::PhoneNumber::Type::MOBILE:
                typeName = "mobile";
                break;
            case Person::PhoneNumber::Type::HOME:
                typeName = "home";
                break;
            case Person::PhoneNumber::Type::WORK:
                typeName = "work";
                break;
        }
        os << "  " << typeName << " phone: " << phone.getNumber().cStr() << std::endl;
    }
    auto employment = personReader.getEmployment();
    switch (employment.which()) {
        case Person::Employment::UNEMPLOYED: {
            os << "  unemployed";
        } break;
        case Person::Employment::EMPLOYER: {
            os << "  employer: " << employment.getEmployer().cStr();
        } break;
        case Person::Employment::SCHOOL: {
            os << "  student at: " << employment.getSchool().cStr();
        } break;
        case Person::Employment::SELF_EMPLOYED: {
            os << "  self-employed";
        } break;
    }
    os << std::endl;
    return os;
}

void printAddressBook(int fd) {
    ::capnp::PackedFdMessageReader message(fd);
    auto addressBook = message.getRoot<addressbook::AddressBook>();
    LOGI << "Print addressbook...";

    for (auto person: addressBook.getPeople()) {
        LOGI << "Person: " << std::endl
             << person;
    }
}